import { existsSync, readdirSync, readFileSync, writeFileSync, copyFileSync } from "fs";
import { join } from "path";
import { cpDir, mkdirsSync } from "../utils/CpFile";
import { IPack, IPackageOptions } from "./IPack";
import { paserIndexJs, hashFile } from "../utils/PaserFile";
import { execFile, exec, spawn } from "child_process";
import { createHash } from "crypto";
import { delFiles } from "../utils/DelFile";
import { PackMgr } from "./PackMgr";
import path = require("path");


export class BasePack implements IPack {

    protected _plat = ''
    protected _projectConfigFile = '';
    protected _projCfg: any;
    protected _layaEnginePlugins: string[] = [];
    protected _mode = '';
    protected _packProjectJsonName: string = ''

    protected _minigamePath: string = '';
    protected _resPath: string[] = [];
    protected _packOpt: IPackageOptions;


    constructor(packProjectJsonName: string) {
        this._packProjectJsonName = packProjectJsonName;
        this._projectConfigFile = 'project.config.json';
    }

    /** 是否有项目的json文件 */
    /* protected _getPorjCfg() {
        let files = readdirSync(join(process.cwd()));
        return files.filter(f => f.indexOf('layapp-') >= 0)[0]
    } */

    packProject(mode = 'dev') {
        this._mode = mode;

        let jsonUrl = join(process.cwd(), this._packProjectJsonName)
        if (!existsSync(jsonUrl)) {
            console.log('no layapp init config, please use: layapp init projname wx');
            PackMgr.inst.packNext();
            return;
        }
        let cfgStr = readFileSync(jsonUrl).toString();
        this._projCfg = JSON.parse(cfgStr);
        let plat = this._plat = this._projCfg.init.plat;

        console.log('start package: =======', this._plat);

        let po: IPackageOptions = this._packOpt = this._projCfg[mode]['packageOptions'];
        // 如果导出目录中，已经存在 project.config.json 则跳过初始化过程
        let projUrl = join(po.outputPath, `${plat}/${plat}game`, this._projectConfigFile)
        if (existsSync(projUrl)) {
            this._startPack(po);
        } else {
            this._initProject()
        }

    }

    protected _getTemplateUrl() {
        return join(__dirname, `../../assets/template/${this._plat}game`);
    }
    protected _getEnginePluginUrl() {
        return join(__dirname, `../../assets/engineplugin`);
    }

    protected _initProject() {
        let from = this._getTemplateUrl()
        let out = join(this._packOpt.outputPath, `${this._plat}/${this._plat}game`);
        // 这里要注意,如果需要打包的项目,没有模板文件,会无法打包.这里后续要考虑优化
        if (!existsSync(from)) {
            console.log(`ERROR!!! ** no ${this._plat}game's template files **`);
            PackMgr.inst.packNext();
            return;
        }

        this._copyInitDir(from, out);
    }

    /** 此方法默认是wx的，其他的打包需要覆盖这个方法 */
    protected _copyInitDir(from: string, out: string) {
        let po: IPackageOptions = this._projCfg[this._mode]['packageOptions'];

        cpDir(from, out, () => {
            // 需要修改一下 project.config.json 中的替换文本
            let pcfUrl = `${this._plat}/${this._plat}game/${this._projectConfigFile}`;
            let pcfTemp = join(po.outputPath, pcfUrl);
            let jsonStr = readFileSync(pcfTemp).toString();

            jsonStr = jsonStr.replace('${APPID}', this._projCfg.init.appid)
            jsonStr = jsonStr.replace('${PROJECT_NAME}', this._projCfg.init.projectName);
            writeFileSync(pcfTemp, jsonStr);

            // 修改index.js
            let fromIndex = join(po.projectBin, 'index.js');
            let { index_txt, plugin_txt, plugin_list } = paserIndexJs(fromIndex, this._layaEnginePlugins);
            let outIndex = join(po.outputPath, `${this._plat}/${this._plat}game/minigame`, 'index.js');
            writeFileSync(outIndex, index_txt);

            // 清除不需要导入的引擎插件
            this._clearLayalibs(plugin_list)

            // 修改game.js
            let gamejs = join(po.outputPath, `${this._plat}/${this._plat}game/minigame`, 'game.js');
            let gamejsTxt = readFileSync(gamejs).toString();
            gamejsTxt = gamejsTxt.replace("/** -REQUIRE_PLUGIN- */", plugin_txt);
            writeFileSync(gamejs, gamejsTxt);

            this._startPack(po);
        })
    }

    /** 待定...这个想使用相对目录来放置layapp的json文件, 不想放到项目目录中 */
    protected _getRelativePath(path: string) {
        return join(process.cwd(), path);
    }

    protected _setGameConfig(configUrl?: string) {
        let config: any = this._projCfg[this._mode]['config'];

        if (!config) {
            config = {};
            return;
        }
        let txt = `window.config = ${JSON.stringify(config)};\n`;
        let po = this._projCfg[this._mode]['packageOptions'];
        configUrl = configUrl ? configUrl : join(po.outputPath, `${this._plat}/${this._plat}game/minigame`, 'config.js');
        writeFileSync(configUrl, txt);
    }

    protected _startPack(packOpt: IPackageOptions) {
        this._setGameConfig()
        this._packOver()
    }

    protected _packOver(shellParams: string[] = []) {

        let scriptUrl = this._projCfg[this._mode]['packageOverScript'];
        if (!scriptUrl) {
            console.log('no script need to run!');
            PackMgr.inst.packNext();
            return;
        }
        let temp: string[] = join(scriptUrl).split(' ');
        let shellUrl = temp[0];
        if (!shellParams.length) shellParams = temp.slice(1);

        console.log('start exec ' + shellUrl + ' ' + shellParams);

        execFile(shellUrl, shellParams, { shell: true }, (err, stdout, stderr) => {
            if (err !== null) {
                console.log('exec error: ' + err);
            }
            console.log(stdout);
            console.log(`${shellUrl} exec over`);
            console.log('============')
            PackMgr.inst.packNext();
        })

    }

    /** 清理无用的引擎插件 */
    protected _clearLayalibs(plugin_list: string[]) {
        let po: IPackageOptions = this._projCfg[this._mode]['packageOptions'];
        let pluginIndexJs = '';
        plugin_list.forEach(js => {
            pluginIndexJs += `require("./${js}");\n`
        });
        let layalibUrl = join(po.outputPath, `${this._plat}/${this._plat}game/minigame/laya-libs`);
        let pluginIndexUrl = join(layalibUrl, 'index.js');
        writeFileSync(pluginIndexUrl, pluginIndexJs);
        let indexHash = hashFile(pluginIndexUrl);
        // console.log("indexHash", indexHash,plugin_list);
        let signatureUrl = join(layalibUrl, 'signature.json');
        let signatureJson = JSON.parse(readFileSync(signatureUrl).toString());
        let temp = [{
            path: 'index.js',
            md5: indexHash
        }];
        plugin_list.forEach(js => {
            for (let d of signatureJson.signature) {
                if (d.path == js) {
                    temp.push(d);
                }
            }
        });
        signatureJson.signature = temp;
        writeFileSync(signatureUrl, JSON.stringify(signatureJson));
        let ignore = plugin_list.slice().concat('index.js', 'plugin.json', 'signature.json')
        delFiles(layalibUrl, ignore);

    }

    protected _cpResIndex = 0;
    protected _cpRes() {
        if (this._cpResIndex >= this._resPath.length) {
            this._packOver();
            return;
        }
        let pathName = this._resPath[this._cpResIndex]
        let toResUrl = join(this._minigamePath, pathName);
        if (this._packOpt.resVer) {
            toResUrl = join(this._packOpt.outputPath, `${this._plat}/${this._packOpt.resVer}/${pathName}`);
        }

        let tempList = pathName.split('/');
        if (tempList[tempList.length - 1].indexOf('.') >= 0) {
            // 是文件
            mkdirsSync(path.dirname(toResUrl))
            copyFileSync(join(this._packOpt.projectBin, pathName), toResUrl);
            console.log(this._mode + ' cp file about: ' + pathName);
            this._cpResIndex++;
            this._cpRes()
        } else {
            // 是目录
            cpDir(join(this._packOpt.projectBin, pathName), toResUrl, () => {
                console.log(this._mode + ' cp dir about: ' + pathName);
                this._cpResIndex++;
                this._cpRes()
            })
        }

    }


    async exec(cmd: string, option?: any) {
        return new Promise((resolve, reject) => {
            var t = Date.now();
            console.log("开始执行:" + cmd);
            exec(cmd, option, (err, stdout, stderr) => {
                if (err) {
                    console.log("执行失败:" + cmd + "\nError:" + err.message);
                    resolve();
                    return;
                }
                console.log("执行成功:" + cmd + "    耗时(" + (Date.now() - t) + ")毫秒");
                resolve(stdout);
            })
        });
    }

    spawn(cmd: string, args: string[], options?: any, updateFunc?: (log: string) => void) {
        let cp = spawn(cmd, args, options);
        cp.stdout.on('data', (data) => {
            console.log(`${data}`);
            updateFunc && updateFunc(data.toString());
            // 输出pid，macos要用: macos无法kill进程树，也无法执行命令获取3000端口pid(没有查询权限)，导致无法kill这个进程
            console.log('vv_qrcode_pid:' + cp.pid);
        });

        cp.stderr.on('data', (data) => {
            console.log(`stderr: ${data}`);
            // console.log(`stderr(iconv): ${iconv.decode(data, 'gbk')}`);
            // reject();
        });

        cp.on('close', (code) => {
            console.log(`子进程退出码：${code}`);
            // resolve();
        });
        return cp;
    }

}